/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.core.services;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import javax.jcr.NamespaceException;
import javax.jcr.RepositoryException;
import offset.nodes.Constants;
import offset.nodes.client.editor.model.messages.TemplateInformation;
import offset.nodes.client.virtual.model.SimpleQuery;
import offset.nodes.client.virtual.model.jcr.QName;
import offset.nodes.client.virtual.model.jcr.nodetype.NodeTypeDefinition;
import offset.nodes.client.virtual.model.jcr.nodetype.PropertyDefinition;
import offset.nodes.server.model.ServerNamespaceRegistry;
import org.apache.jackrabbit.core.nodetype.NodeDef;
import org.apache.jackrabbit.core.nodetype.NodeTypeDef;
import org.apache.jackrabbit.core.nodetype.PropDef;
import org.apache.jackrabbit.spi.Name;

/**
 * Check, if the node type information is used by the transformation.
 *
 * @author Walter L�tgenau
 */
class TemplateUsageDetector {

    protected final String MATCH = "match=\"";
    HashSet<String> templates = new HashSet<String>();
    List<String> secondaryQueryRoots = new LinkedList<String>();

    public TemplateUsageDetector(String transformation) {
        parse(transformation);
    }

    /**
     * Parse the transformation code.
     * 
     * @param transformation
     */
    protected void parse(String transformation) {
        String[] templateDeclarations = transformation.split("<xsl:template ");

        for (String templateDeclaration : templateDeclarations) {
            int match = templateDeclaration.indexOf(MATCH);
            if (match < 0)
                continue;

            String name = templateDeclaration.substring(match + MATCH.length(), templateDeclaration.indexOf("\"", match + MATCH.length()));
            if (isSecondaryQueryRoot(name))
                secondaryQueryRoots.add(name);

            templates.add(name);
        }
    }

    /**
     * Is the template path used by the transformation?
     *
     * @param path
     * @return
     */
    public boolean isUsed(String path) {
        return templates.contains(path);
    }

    protected boolean isSecondaryQueryRoot(String name) {
        if (name.matches("/[a-zA-Z0-9]*/[a-zA-Z0-9:]*"))
            return true;
        return false;
    }

    public List<String> getSecondaryQueryRoots() {
        return secondaryQueryRoots;
    }
}

/**
 * Create the per template information
 * necessary for the virtual data editor.
 * 
 * @author Walter L�tgenau
 */
public class TemplateInformationCreator {

    ServerNamespaceRegistry namespaces;
    HashMap<String, NodeTypeDef> nodeTypes;
    TemplateUsageDetector templateUsageDetector;
    HashMap<String, String> secondaryQueries;

    public TemplateInformationCreator(ServerNamespaceRegistry namespaces, HashMap<String, NodeTypeDef> nodeTypes, String transformation, HashMap<String, String> secondaryQueries) {
        this.namespaces = namespaces;
        this.nodeTypes = nodeTypes;
        this.secondaryQueries = secondaryQueries;
        this.templateUsageDetector = new TemplateUsageDetector(transformation);
    }

    public HashMap<String, TemplateInformation> createTemplateInformation(String nodeTypeName) throws Exception {
        HashMap<String, TemplateInformation> result = new HashMap<String, TemplateInformation>();

        addNodeTypeInformation(result, nodeTypeName, nodeTypeName, false);
        for (String secondaryQueryRoot : templateUsageDetector.getSecondaryQueryRoots())
            addNodeTypeInformation(result, secondaryQueryRoot.substring(secondaryQueryRoot.lastIndexOf(Constants.PATH_SEPARATOR) + 1), secondaryQueryRoot, 
                isMultiple(secondaryQueryRoot, nodeTypeName));

        return result;
    }

    /**
     * Return the default node type definition.
     * If the child has no such definition, return the first primary type
     * 
     * @param child
     * @return
     */
    protected Name getPrimaryType(NodeDef child) {
        if (child.getDefaultPrimaryType() != null)
            return child.getDefaultPrimaryType();
        return child.getRequiredPrimaryTypes()[0];
    }

    /**
     * Add node type information for the node type nodeTypeName at the given path.
     * 
     * @param result Add the information to this result.
     * @param nodeTypeName
     * @param path
     * @param occursMultiple
     * @throws NamespaceException
     * @throws RepositoryException
     * @throws Exception
     */
    protected void addNodeTypeInformation(HashMap<String, TemplateInformation> result,
            String nodeTypeName, String path, boolean occursMultiple) throws NamespaceException, RepositoryException, Exception {
        if (!templateUsageDetector.isUsed(path))
            return;

        NodeTypeDef nodeTypeDef = (NodeTypeDef) nodeTypes.get(nodeTypeName);
        NodeTypeDefinition nodeTypeDefinition = ToNodeTypeDefinitionConverter.convert(nodeTypeDef);

        result.put(path, new TemplateInformation(path, nodeTypeDefinition, occursMultiple));

        NodeDef[] children = nodeTypeDef.getChildNodeDefs();

        String typeName = null;
        for (int i = 0; i < children.length; i++) {
            typeName = namespaces.getPrefix(getPrimaryType(children[i]).getNamespaceURI()) + ":" + getPrimaryType(children[i]).getLocalName();

            String name = null;
            if (children[i].getName().getNamespaceURI().equals(QName.ANY_NAME.getNamespaceURI()) && children[i].getName().getLocalName().equals(QName.ANY_NAME.getLocalName()))
                name = typeName;
            else
                name = namespaces.getPrefix(children[i].getName().getNamespaceURI()) + ":" + children[i].getName().getLocalName();

            String childPath = path + Constants.PATH_SEPARATOR + name;

            addNodeTypeInformation(result, typeName, childPath, children[i].allowsSameNameSiblings());
        }

        PropertyDefinition[] properties = nodeTypeDefinition.getDeclaredPropertyDefinitions();

        for (int i = 0; i < properties.length; i++) {
            String name = path + "/@" + namespaces.getPrefix(properties[i].getName().getNamespaceURI()) + ":" + properties[i].getName().getLocalName();
            if (!templateUsageDetector.isUsed(name))
                continue;

            result.put(name, new TemplateInformation(name, properties[i], false));
        }

        for (Name superType : nodeTypeDef.getSupertypes()) {
            addNodeTypeInformation(result, namespaces.toPrefixName(superType), path, occursMultiple);
        }
    }

    /**
     * Is this a multiple result secondary query?
     *
     * @param secondaryQueryRoot
     * @return
     */
    protected boolean isMultiple(String secondaryQueryRoot, String primaryNodeTypeName) {
        String secondaryQueryKey = secondaryQueryRoot.substring(1, secondaryQueryRoot.substring(1).indexOf(Constants.PATH_SEPARATOR) + 1);
        String secondaryQuery = secondaryQueries.get(secondaryQueryKey);
        if (secondaryQuery == null)
            return true;

        SimpleQuery query = new SimpleQuery(secondaryQuery);

        if (query.getReferenceDirection() == SimpleQuery.REFERENCE_FROM_THIS) {
            String secondaryQueryReferenceProperty = query.getReferenceProperty();
            NodeTypeDef nodeTypeDef = (NodeTypeDef) nodeTypes.get(primaryNodeTypeName);
            for (PropDef propertyDefinition : nodeTypeDef.getPropertyDefs()) {
                if (namespaces.toPrefixName(propertyDefinition.getName()).equals(secondaryQueryReferenceProperty)) {
                    return propertyDefinition.isMultiple();
                }
            }

            for (Name supertype : nodeTypeDef.getSupertypes()) {
                String supertypeName = namespaces.toPrefixName(supertype);
                nodeTypeDef = (NodeTypeDef) nodeTypes.get(supertypeName);
                for (PropDef propertyDefinition : nodeTypeDef.getPropertyDefs()) {
                    if (namespaces.toPrefixName(propertyDefinition.getName()).equals(secondaryQueryReferenceProperty)) {
                        return propertyDefinition.isMultiple();
                    }
                }
            }
        } else {
            String secondaryNodeType = query.getNodeType();
            // check for marker mixin oneToOne, which marks a non-multiple reference
            for (Name supertype : nodeTypes.get(secondaryNodeType).getSupertypes()) {
                if (Constants.TYPE_ONE_TO_ONE.equals(namespaces.toPrefixName(supertype)))
                    return false;
                }
        }

        return true;
    }

    /**
     * Extract the secondary query reference property of the primary
     *
     * @param secondaryQuery
     * @return the reference property
     */
    protected String getSecondaryQueryReferenceProperty(String secondaryQuery) {
        String filter = secondaryQuery.substring(secondaryQuery.lastIndexOf("[") + 1, secondaryQuery.lastIndexOf("]"));
        int start = filter.indexOf(Constants.QUERY_PRIMARY) + Constants.QUERY_PRIMARY.length() + 2;
        String reference = filter.substring(start, filter.indexOf(")", start));

        return reference;
    }
}
